#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include "lccc.h"


#define INLINE inline static

#define lua_isstring(a,idx) (lua_type(a,idx)==LUA_TSTRING)
#ifndef lcheck_toptr
#define lcheck_toptr _lcheck_toptr
static void* _lcheck_toptr(lua_State*L,int idx){
  void* data = 0;
  if(lua_isstring(L,idx)){
    data = (void*)lua_tostring(L,idx);
  }
  else if(lua_islightuserdata(L,idx)/*||lua_isuserdata(L,idx)*/){
    data = (void*)lua_touserdata(L,idx);
  }
  else if(lua_isuserdata(L,idx)){
    data = *(void**)lua_touserdata(L,idx);
  }
  else{
    luaL_argerror(L,idx,"type error:pointer_type");
  };
  return data;
};
#endif
#if 0
INLINE void MCopy(void*d,void*s,int siz){
  if(siz==1)
    *(uint8_t*)d = *(uint8_t*)s;
  else if(siz==2)
    *(uint16_t*)d = *(uint16_t*)s;
  else if(siz==4)
    *(uint32_t*)d = *(uint32_t*)s;
  else if(siz==8)
    *(uint64_t*)d = *(uint64_t*)s;
  else
    memcpy(d,s,siz);
}

#include <string.h>
#define TArrayName "TArray"
typedef struct{
  void* p;
  char* typeName;
}TArray;

#define DEFF(t) \
static int larray##t##_get(lua_State*L){ \
  t*p = (t*)lcheck_toptr(L,1);               \
  int idx = luaL_checkinteger(L,2);      \
  lua_pushnumber(L,p[idx]);              \
  return 1;                              \
}                                        \
static int larray##t##_set(lua_State*L){ \
  t*p = (t*)lcheck_toptr(L,1);           \
  int idx = luaL_checkinteger(L,2);      \
  t f = luaL_checknumber(L,3);       \
  p[idx] = f;                           \
  return 0;                             \
}

#define DEFI(t) \
static int larray##t##_get(lua_State*L){ \
  t*p = (t*)lcheck_toptr(L,1);               \
  int idx = luaL_checkinteger(L,2);      \
  lua_pushinteger(L,p[idx]);              \
  return 1;                              \
}                                        \
static int larray##t##_set(lua_State*L){ \
  t*p = (t*)lcheck_toptr(L,1);           \
  int idx = luaL_checkinteger(L,2);      \
  t f = (t)luaL_checkinteger(L,3);       \
  p[idx] = f;                           \
  return 0;                             \
}

DEFF(float)
DEFF(double)
DEFI(int)
DEFI(short)
DEFI(char)

DEFI(int8_t)
DEFI(int16_t)
DEFI(int32_t)
DEFI(int64_t)

DEFI(uint8_t)
DEFI(uint16_t)
DEFI(uint32_t)
DEFI(uint64_t)

static int larray_get(lua_State*L){
  TArray *array;
  int idx = luaL_checkinteger(L,2);
  if(lua_islightuserdata(L,1)){
    //array = lua_touserdata(L,1);
    luaL_argerror(L,1,"error array");
  }
  else if(lua_isuserdata(L,1)){
    array = lua_touserdata(L,1);
  }
  void**p = array->p;
  char *typeName = strdup(array->typeName);
  int len = strlen(typeName);
  if(len>0 && typeName[len]=='*'){
    typeName[len-1] = 0;
    if(luaL_getmetatable(L,typeName)){
      *(void**)lua_newuserdata(L,sizeof(void**)) = p[idx];
      luaL_setmetatable(L,typeName);
    }
    else{
      TArray *darray = lua_newuserdata(L,sizeof(TArray));
      darray->p = p[idx];
      darray->typeName = typeName;
      luaL_setmetatable(L,TArrayName);
    }
  }
  else{
    return 0;
  }
  return 1;
}

static int larray_set(lua_State*L){
  TArray*array = luaL_checkudata(L,1,TArrayName);
  int idx = luaL_checkinteger(L,2);
  void*p = lcheck_toptr(L,3);
  void**dp = array->p;
  dp[idx] = p;
  return 0;
}

static int larray_gc(lua_State*L){
  TArray*array = luaL_checkudata(L,1,TArrayName);
  free(array->typeName);
  return 0;
}

static int larray_tostring(lua_State*L){
  TArray*array = luaL_checkudata(L,1,TArrayName);
  int len = strlen(array->typeName);
  char*s = calloc(len+0x20,1);
  sprintf(s,"%s%p",array->typeName,array->p);
  lua_pushstring(L,s);
  free(s);
  return 1;
}

static int larrayCreateMetatable(lua_State*L){
  #define DEFSMETA(t) if(luaL_newmetatable(L,#t"*")){ \
      lua_pushcfunction(L,larray##t##_get);           \
      lua_setfield(L,-2,"__index");                   \
      lua_pushcfunction(L,larray##t##_set);           \
      lua_setfield(L,-2,"__newindex");                \
      lua_pushstring(L,#t"*");                        \
      lua_setfield(L,-2,"name");                      \
      lua_pop(L,1);                                   \
    }

    DEFSMETA(float)
    DEFSMETA(double)
    DEFSMETA(int)
    DEFSMETA(short)
    DEFSMETA(char)
    DEFSMETA(uint64_t)
    DEFSMETA(uint32_t)
    DEFSMETA(uint16_t)
    DEFSMETA(uint8_t)
    DEFSMETA(int64_t)
    DEFSMETA(int32_t)
    DEFSMETA(int16_t)
    DEFSMETA(int8_t)

  if(luaL_newmetatable(L,TArrayName)){
    lua_pushcfunction(L,larray_get);
    lua_setfield(L,-2,"__index");
    lua_pushcfunction(L,larray_set);
    lua_setfield(L,-2,"__newindex");
    lua_pushcfunction(L,larray_gc);
    lua_setfield(L,-2,"__gc");
    lua_pushcfunction(L,larray_tostring);
    lua_setfield(L,-2,"__tostring");
  
    lua_pushstring(L,TArrayName);
    lua_setfield(L,-2,"name");
    lua_pop(L,1);
  }
  return 0;
}

static int clarrayNew(lua_State*L,void*ptr,const char*type){
  TArray *darray = lua_newuserdata(L,sizeof(TArray));
  darray->p = ptr;
  darray->typeName = strdup(type);
  luaL_setmetatable(L,TArrayName);
  return 1;
}

static int larrayNew(lua_State*L){
  void*p = lcheck_toptr(L,1);
  const char*type = luaL_checkstring(L,2);
  return clarrayNew(L,p,type);
}

static int lgetm(lua_State*L){
  const char*s = luaL_checkstring(L,1);
  luaL_getmetatable(L,s);
  return 1;
}

static int lsetm(lua_State*L){
  if(lua_islightuserdata(L,1)){
    *(void**)lua_newuserdata(L,sizeof(void**)) = lua_touserdata(L,1);
  }
  else
    lua_pushvalue(L,1);
  
  if(lua_istable(L,2)){
    lua_pushvalue(L,2);
    lua_setmetatable(L,-2);
    return 1;
  }
  else if(lua_isstring(L,2)){
    luaL_setmetatable(L,lua_tostring(L,2));
    return 1;
  }
  return 0;
}
#endif

static int lLCC_add_argv(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int ret = (int)LCC_add_argv(a1,a2);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_buffer_add(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  void* a3 = (void*)lcheck_toptr(L,3);
  int ret = (int)LCC_buffer_add(a1,a2,a3);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_buffer_getidx(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int ret = (int)LCC_buffer_getidx(a1,a2);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_buffer_removeAll(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  int ret = (int)LCC_buffer_removeAll(a1);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_buffer_set(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  int a2 = (int)luaL_checkinteger(L,2);
  void* a3 = (void*)lcheck_toptr(L,3);
  int ret = (int)LCC_buffer_set(a1,a2,a3);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_completion_get(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int a3 = (int)luaL_checkinteger(L,3);
  int a4 = (int)luaL_checkinteger(L,4);
  int ret = (int)LCC_completion_get(a1,a2,a3,a4);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_completion_proc(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int a3 = (int)luaL_checkinteger(L,3);
  int a4 = (int)luaL_checkinteger(L,4);
  int ret = (int)LCC_completion_proc(a1,a2,a3,a4);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_completion_proc_callback(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int a3 = (int)luaL_checkinteger(L,3);
  int a4 = (int)luaL_checkinteger(L,4);
  void* a5;
  if(lua_isnil(L,5))
      a5 = 0;
  else
      a5 = (void*)lcheck_toptr(L,5);
  void* a6 = (void*)lcheck_toptr(L,6);
  int ret = (int)LCC_completion_proc_callback(a1,a2,a3,a4,a5,a6);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_free(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  LCC_free(a1);
  return 0;
}

static int inied_metatable = 0;
static int lcc_gc(lua_State*L){
    void**p = luaL_checkudata(L,1,"LCC");
    if(*p){
        LCC_free(*p);
    }
    return 0;
}

static int lLCC_init(lua_State*L){
  void* ret = (void*)LCC_init();
  if(!inied_metatable){
      luaL_newmetatable(L,"LCC");
      lua_pushcfunction(L,lcc_gc);
      lua_setfield(L,-2,"__gc");
      inied_metatable = 1;
  }
  //lua_pushlightuserdata(L,ret);
  //printf("lcc:%p",ret);
  *(void**)lua_newuserdata(L,sizeof(void**)) = ret;
  luaL_setmetatable(L,"LCC");
  return 1;
}

static int lLCC_init_clang(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  int ret = (int)LCC_init_clang(a1);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_set_file(lua_State*L){
  void* a1 = (void*)lcheck_toptr(L,1);
  void* a2 = (void*)lcheck_toptr(L,2);
  int ret = (int)LCC_set_file(a1,a2);
  lua_pushinteger(L,ret);
  return 1;
}

static int lLCC_set_match(lua_State*L){
    void* a1 = (void*)lcheck_toptr(L,1);
    void* a2 = (void*)lcheck_toptr(L,2);
    int ret = (int)LCC_set_match(a1,a2);
    lua_pushinteger(L,ret);
    return 1;
}

static int lLCC_set_tuopt(lua_State*L){
    void* a1 = (void*)lcheck_toptr(L,1);
    int a2 = luaL_checkinteger(L,2);
    int ret = (int)LCC_set_tuopt(a1,a2);
    lua_pushinteger(L,ret);
    return 1;
}

#if defined(__cplusplus)
extern "C"{
#endif

int test_openlibs(lua_State*L); //openlibs
int luaopen_test(lua_State*L); //luaopen

#if defined(__cplusplus)
}
#endif

int Lget_callback_userdata(lua_State*L);
int Lget_callback(lua_State*L);

int llcc_openlibs(lua_State*L){
  const char*name = "llcc";
#define lregister(L,n) lua_register(L,#n,l##n)
#if 0
  larrayCreateMetatable(L);
  lregister(L,arrayNew);
  lregister(L,setm);
  lregister(L,getm);
#endif
  //export Lua functions:12

  lregister(L,LCC_add_argv);
  lregister(L,LCC_buffer_add);
  lregister(L,LCC_buffer_getidx);
  lregister(L,LCC_buffer_removeAll);
  lregister(L,LCC_buffer_set);
  lregister(L,LCC_completion_get);
  lregister(L,LCC_completion_proc);
  lregister(L,LCC_completion_proc_callback);
  lregister(L,LCC_free);
  lregister(L,LCC_init);
  lregister(L,LCC_init_clang);
  lregister(L,LCC_set_file);
  lregister(L,LCC_set_match);
  lregister(L,LCC_set_tuopt);
  //lTypeInit(L);


  lua_register(L,"LCC_get_callback_userdata",Lget_callback_userdata);
  lua_register(L,"LCC_get_callback_func",Lget_callback);


//enums
#ifndef lregisterInt
#ifndef USE_HEADER
#define lregisterInt(L,name,value) lua_pushinteger(L,value); lua_setglobal(L,#name);
#else
#define lregisterInt(L,name,value) lua_pushinteger(L,name); lua_setglobal(L,#name);
#endif
#endif


  //export Lua enum:0



  return 0;
};

int luaopen_llcc(lua_State*L){
  return llcc_openlibs(L);
};

